/*******************************************************************************
 * SVN­Versioning-Information:
 * ­­­­­­­­­­­­­­­­­­­­­­­­
 * $LastChangedRevision: 6 $
 * $LastChangedBy: c.a.duevel $
 * $LastChangedDate: 2009-08-26 15:12:37 +0000 (Wed, 26 Aug 2009) $
 * $HeadURL: http://robofight.googlecode.com/svn/trunk/ClientJava/src/de/hft_stuttgart/robofight/client/java/JPlayer.java $
 ******************************************************************************/
package de.hft_stuttgart.robofight.client.java;

import de.hft_stuttgart.botwar.common.commands.ChatCmd;
import de.hft_stuttgart.botwar.common.commands.Cmd;
import de.hft_stuttgart.botwar.common.commands.GameOverCmd;
import de.hft_stuttgart.botwar.common.commands.InitGameCmd;
import de.hft_stuttgart.botwar.common.commands.KickCmd;
import de.hft_stuttgart.botwar.common.interfaces.IRemotePlayer;
import de.hft_stuttgart.botwar.common.models.GameInfo;
import de.hft_stuttgart.botwar.common.models.GameResultInfo;
import de.hft_stuttgart.botwar.common.models.PlayerInfo;
import de.hft_stuttgart.botwar.common.models.RobotInfo;
import java.rmi.RemoteException;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author ralf.dauenhauer@gmx.de, michael.rieker@web.de
 */
public class JPlayer implements Runnable {

    private IRemotePlayer player;
    private int gameID = 999;
    private JRunningGame runningGame = null;
    private List<ChatCmd> gameLobbyChatList = new ArrayList<ChatCmd>();
    private List<ChatCmd> lobbyChatList = new ArrayList<ChatCmd>();
    private List<Integer> activeRobotIDs = new ArrayList<Integer>();
    private List<String> gameResultList = new ArrayList<String>();
    private Thread thread;
    private ChatCmd cc;
    private KickCmd kk = null;
    private Boolean isPolling = true;
    private Boolean playerIsRunningGame = false;
    private final int namePerPage = 100;
    private static final long TIME = 1000L;

    /**
     * creates a new JPlayer object.
     * @param player needs the IRemotePlayer as param
     */
    public JPlayer(final IRemotePlayer player) {
        this.player = player;
        thread = new Thread(this);
        thread.start();
    }

    /**
     * Do nothing.
     */
    public JPlayer() {
        //For JUnit
    }

    /**
     * client thread, used for polling all over the time.
     */
    public final void run() {
        List<Cmd> commands;
        while (true) {
            while (isPolling()) {
                try {
                    commands = player.poll();
                    for (Cmd cmd : commands) {
                        if (cmd instanceof ChatCmd) {
                            cc = (ChatCmd) cmd;
                            switch (cc.getChatType()) {
                                case LOBBY:
                                    lobbyChatList.add(cc);
                                    gameLobbyChatList.add(cc);
                                    break;
                                case GAMELOBBY:
                                    gameLobbyChatList.add(cc);
                                    break;
                                case PRIVATE:
                                    if (this.gameID == 999) {
                                        lobbyChatList.add(cc);
                                    }
                                    gameLobbyChatList.add(cc);
                                    break;
                                default:
                            }
                        }
                        if (cmd instanceof KickCmd) {
                            kk = (KickCmd) cmd;
                        }
                        if (cmd instanceof InitGameCmd) {
                            createRunningGame();
                            playerIsRunningGame = true;
                            // stop polling here, now polling in jRunningGame
                            setPolling(false);
                            break;
                        }
                    }
                    Thread.sleep(TIME);
                } catch (RemoteException ex) {
                    Logger.getLogger(JPlayer.class.getName()).log(
                            Level.SEVERE, null, ex);

                } catch (InterruptedException ex) {
                    Logger.getLogger(JPlayer.class.getName()).log(
                            Level.SEVERE, null, ex);
                }
            }
            try {
                Thread.sleep(TIME);
            } catch (InterruptedException ex) {
                Logger.getLogger(JPlayer.class.getName()).log(Level.SEVERE,
                        null, ex);
            }
        }
    }

    /**
     * check if a player was kicked.
     * @return kicked
     */
    public final String kicked() {
        if (this.kk == null) {
            return "";
        } else {
            return "You´ve been kicked by " + this.kk.getKickedBy();
        }
    }

    /**
     * resets the kick object, temporarily.
     */
    public final void resetKicked() {
        this.kk = null;
    }

    /**
     * returns a list contains all bots of the player.
     * @return robotIDs
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final List<Integer> getPlayerRobotIDs() throws RemoteException {
        List<Integer> robotIDs = new ArrayList<Integer>();
        if (player.getAvailableRobots() != null) {
            List<RobotInfo> ri = player.getAvailableRobots();
            for (int i = 0; i <
                    ri.size(); i++) {

                robotIDs.add((Integer) ri.get(i).getRobotID());
            }

        }
        return robotIDs;
    }

    /**
     * returns a list contains all bots of the player.
     * @param robotID needs the botID for all infos
     * @return String short info of one bot
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final String getPlayerRobotInfo(
            final Integer robotID)
            throws RemoteException {
        List<RobotInfo> ri = player.getAvailableRobots();
        StringBuilder bi = new StringBuilder();
        for (int i = 0; i <
                ri.size(); i++) {
            if (ri.get(i).getRobotID() == robotID) {
                RobotInfo botInfo = ri.get(i);
                bi.append(botInfo.getRobotName()).
                        append(" | Hlth: ").
                        append(botInfo.getShield().getHealth()).
                        append(" | Damg: ").
                        append(botInfo.getFirePower()).
                        append(" | ").
                        append(botInfo.getCompleteCost()).append("$");
                return bi.toString();
            }
        }
        return "";
    }

    /**
     * returns full robotInfo , used for tooltip.
     * @param robotID needs the bot id to selected all infos of this
     * @return getFullPlayerRobotInfo info of one robots of a player
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final String getFullPlayerRobotInfo(
            final Integer robotID)
            throws RemoteException {

        List<RobotInfo> ri = player.getAvailableRobots();
        for (int i = 0; i <
                ri.size(); i++) {
            if (ri.get(i).getRobotID() == robotID) {
                return ri.get(i).getChassis().getName() + " | " +
                        ri.get(i).getShield().getName() + " | " +
                        ri.get(i).getArmament().getName()+ " | Level: " +
                        ri.get(i).getLevelRank();
            }
        }
        return "";
    }

    /**
     * Sets the player ready to start a game.
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final void setReady() throws RemoteException {
        this.player.ready();
    }

    /**
     * returns the IRemotePlayer object.
     * @return IRemotePlayer
     */
    public final IRemotePlayer getPlayer() {
        return player;
    }

    /**
     * Returns the current message as a List.
     * @return gameLobbyChatList contains the messages.
     */
    public final List<ChatCmd> getLobbyChatMsg() {
        return lobbyChatList;
    }

    /**
     * returns the game lobby chat messages.
     * @return gameLobbyChatList returns a list contains all messages in lobby
     */
    public final List<ChatCmd> getGameLobbyChatMsg() {
        return this.gameLobbyChatList;
    }

    /**
     * clear all chatMessages
     */
    public final void cleanChatMessages() {
        this.gameLobbyChatList.clear();
        this.lobbyChatList.clear();
    }

    /**
     * creates a new Game with following parameters.
     * @param gameName needs the gameName as gameID
     * @param mapName the selected map of the game
     * @param maxPlayers as gamesettings for max players
     * @param maxBots as gamesettings for max bots in a game
     * @param maxPlayerLevel gamesettings for the level of the game
     * @return boolean true or false if creating game was successful or not
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final boolean createGame(final String gameName, final String mapName,
            final String maxPlayers, final String maxBots,
            final String maxPlayerLevel)
            throws RemoteException {

        try {
            setGameID(player.createGame(
                    gameName,
                    mapName,
                    Integer.parseInt(maxPlayers),
                    Integer.parseInt(maxBots),
                    Integer.parseInt(maxPlayerLevel)));
            player.enterGame(getGameID());
            return true;
        } catch (NumberFormatException nfe) {
            return false;
        }

    }

    /**
     * returns a list contains all game ids as String.
     * @return gameList list of all games.
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final List<String> getListOfGames() throws RemoteException {
        List gameList = new ArrayList();
        for (Iterator<GameInfo> it =
                player.getGameInfos().iterator(); it.hasNext();) {

            GameInfo info = it.next();
            gameList.add(info.getGAME_ID());
        }
        return gameList;
    }

    /**
     * sets the active robot for game initialization.
     * @param robotID sets this bot active
     */
    public final void setActiveBots(final Integer robotID) {
        if (activeRobotIDs.contains(robotID)) {
            activeRobotIDs.remove(robotID);
        } else {
            activeRobotIDs.add(robotID);
        }
    }

    /**
     * returns gameInfos for a given gameID.
     * @param gameID needs the gameID to get the infos
     * @return player.getGameInfo(gameID) game infos
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final GameInfo getGameInfo(
            final Integer gameID) throws RemoteException {
        return player.getGameInfo(gameID);
    }

    /**
     * returns gameInfos for a given gameID.
     * @param gameID needs the id of the game
     * @return player.getGameInfo(Integer.valueOf(gameID)).running
     * return the status of the game
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final boolean getGameStatus(final String gameID)
            throws RemoteException {

        return player.getGameInfo(Integer.valueOf(gameID)).running;
    }

    /**
     * returns gameFullInfos for a given gameID.
     * @param gameID needs the gameID as param
     * @return String a short game info
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final String getShortGameInfo(
            final String gameID) throws RemoteException {
        GameInfo gi = player.getGameInfo(Integer.valueOf(gameID));
        StringBuilder bi = new StringBuilder();
        bi.append(gi.getGameName()).append(" | Map: ").
                append(gi.getMapName()).append(" | MaxPlayers: ").
                append(gi.getMaxPlayers()).append(" | Level: ").
                append(gi.getMaxPlayerLevel());
        return bi.toString();
    }

    /**
     * returns gameFullInfos for a given gameID.
     * @param gameID needs the gameID as param
     * @return String a full game info
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final String getFullGameInfo(
            final String gameID) throws RemoteException {
        GameInfo gi = player.getGameInfo(Integer.valueOf(gameID));
        StringBuilder bi = new StringBuilder();
        bi.append("Owner: ").append(gi.getGameOwner()).
                append(" | PlayersJoined: ").append(gi.getPlayers().size()).
                append(" | MaxBots: ").
                append(gi.getMaxPlayerLevel());
        return bi.toString();
    }

    /**
     * returns a list contains all player ids as String.
     * @return playerList existingGames
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final List<String> getListOfPlayers() throws RemoteException {
        try {
            return this.player.getPlayerInfos();
        } catch (IllegalStateException exp) {
            return null;
        }
    }

    /**
     * returns player infos for a given playerID.
     * @param playerID needs the playerID to get the infos
     * @return pInfo.getPlayerID() playerInfos
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final String getPlayerInfos(
            final String playerID) throws RemoteException {
        PlayerInfo pInfo = this.player.getPlayerInfo(playerID);
        return pInfo.getPlayerID();
    }

    /**
     * returns player infos for a given playerID.
     * @param playerID needs the playerID as param
     * @return playerInfos full player infos
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final String getFullPlayerInfo(
            final String playerID)
            throws RemoteException {

        PlayerInfo pi = this.player.getPlayerInfo(playerID);
        StringBuilder sb = new StringBuilder();
        sb.append("Won: ").append(pi.getWonMatches()).
                append(" | Lost:").append(pi.getLostMatches()).
                append(" | Draw: ").append(pi.getDrawMatches()).
                append(" | FightTime:").
                append(formatTime("" + pi.getPlayingTime()));
        return sb.toString();
    }

    /**
     * returns short player infos for a given playerID.
     * @param playerID needs the playerID as param
     * @return shortPlayerInfo info of a player.
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final String getShortPlayerInfo(
            final String playerID)
            throws RemoteException {

        PlayerInfo pi = this.player.getPlayerInfo(playerID);
        StringBuilder sb = new StringBuilder();
        sb.append(pi.getPlayerID()).
                append(" | Level: ").append(pi.getLevel()).
                append(" | Exp: ").append(pi.getExperience()).
                append(" | Rank: ").append(pi.getRanking());
        return sb.toString();
    }

    /**
     * returns playerIDs for a given gameID.
     * @return player.getPlayerInfos(this.gameID) a list of players
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final List<String> getPlayersByGameID() throws RemoteException {
        try {
            return this.player.getPlayerInfos(this.gameID);
        } catch (IllegalStateException exp) {
            return null;
        }
    }

    /**
     * Sets the current game id.
     * @param id the id of the game
     */
    public final void setGameID(final int id) {
        this.gameID = id;
    }

    /**
     * returns the game id.
     * @return gameID int of the game id
     */
    public final int getGameID() {
        return this.gameID;
    }

    /**
     * formats a millisecond long into a formated time HH:MM:SS 
     * @param time in milliseconds
     * @return fTime HH:MM:SS
     */
    public final String formatTime(String time) {
        NumberFormat timeFormat = new DecimalFormat("00");
        double timeAsSeconds = Math.max(Long.parseLong(time), 0) / 1000;
        double pSeconds = timeAsSeconds % 60;
        double timeAsMinutes = (timeAsSeconds - pSeconds) / 60;
        double pMinutes = timeAsMinutes % 60;
        double timeAsHours = (timeAsMinutes - pMinutes) / 60;
        double pHours = timeAsHours;
        StringBuilder fTime = new StringBuilder();
        fTime.append(timeFormat.format(pHours < 0 ? 0 : Math.floor(pHours)));
        fTime.append(":").append(timeFormat.format(pMinutes < 0 ? 0 :
            Math.floor(pMinutes)));
        fTime.append(":").append(timeFormat.format(pSeconds < 0 ? 0 :
            Math.floor(pSeconds)));
        return fTime.toString();
    }

    /**
     * creates a new JRunningGame object.
     */
    public final void createRunningGame() {
        this.runningGame = new JRunningGame(this);
    }

    /**
     * returns true if the player is running a game.
     * @return playerIsRunningGame the object of the running game
     */
    public final boolean getPlayerIsRunningGame() {
        return this.playerIsRunningGame;
    }

    /**
     * set player is running a game to false.
     */
    public final void disablePlayerIsRunningGame() {
        this.playerIsRunningGame = false;
        this.setPolling(true);
    }

    /**
     * returns the jrunningGame object.
     * @return runningGame the object of the running game
     */
    public final JRunningGame getRunningGame() {
        return this.runningGame;
    }

    /**
     * Logout a player.
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final void logout() throws RemoteException {
        this.thread.stop();
        player.logout();
    }

    /**
     * sets the player to ready to init.
     * @return String returns a ErrorMessage or not
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final String setReadyToInit() throws RemoteException {
        try {
            player.readyToInit(activeRobotIDs);
        } catch (IllegalArgumentException exp) {
            return exp.getMessage();
        }
        return "";
    }

    /**
     * set back to "not ready" when in a game lobby.
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final void setUnReadyToInit() throws RemoteException {
        player.setUnReady();
    }

    /**
     * returns true, if a game is running.
     * @param gameId needs the gameID as param
     * @return player.getGameInfo(Integer.parseInt(gameId)).running if a game is
     * running
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final boolean isRunning(final String gameId)
            throws RemoteException {
        return player.getGameInfo(Integer.parseInt(gameId)).running;
    }

    /**
     * sends a chat message for gamelobby or private or to lobby
     * @param message needs the current message as param
     * @return returns false if the recipient does not exists
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final String sendGameChatMsg(final String message)
            throws RemoteException {
        if (message.length() == 0){
            return "";
        }
        //check if the user want to send a private or lobby message
        if (message.startsWith("/")) {
            if (message.startsWith("/lobby:")) {
                getPlayer().sendChatMsg(IRemotePlayer.CHATTYPE.LOBBY, message,
                        "");
            } else {
                try {
                    String recipient = message.substring(
                            1, message.indexOf(":"));
                    getPlayer().sendChatMsg(IRemotePlayer.CHATTYPE.PRIVATE,
                            message, recipient);
                } catch (IllegalArgumentException exp) {
                    return exp.getMessage();
                } catch (StringIndexOutOfBoundsException exp) {
                    return "The correct syntax for private or lobby message is "
                            +"'/username:' or '/lobby:'";
                }
            }
        } else {
            getPlayer().sendChatMsg(IRemotePlayer.CHATTYPE.GAMELOBBY, message,
                    "");
        }
        return "";
    }

    /**
     * Sends a message.
     * @param message needs the current message as param
     * @return returns false if the recipient does not exists
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final String sendLobbyChatMsg(final String message)
            throws RemoteException {
        if (message.length() == 0){
            return "";
        }
        //check if the user want to send a private message
        if (message.startsWith("/")) {
            try {
                String recipient = message.substring(1, message.indexOf(":"));
                getPlayer().sendChatMsg(IRemotePlayer.CHATTYPE.PRIVATE, message,
                        recipient);
            } catch (IllegalArgumentException exp) {
                return exp.getMessage();
            } catch (StringIndexOutOfBoundsException exp) {
                return "The correct syntax for private or lobby message is " +
                        "'/username:' or '/lobby:'";
            }
        } else {
            getPlayer().sendChatMsg(IRemotePlayer.CHATTYPE.LOBBY, message, "");
        }
        return "";
    }

    /**
     * switch polling on or off.
     * @param state sets the state for polling true or false
     */
    public final void setPolling(final boolean state) {
        this.isPolling = state;
        if (state) {
            this.playerIsRunningGame = false;
            this.runningGame = null;
            this.activeRobotIDs.clear();
        }
    }

    /**
     * kicks player out of a game.
     * @param playerID needs the playerID to kick
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final void kickPlayer(final String playerID) throws RemoteException {
        this.player.kickPlayer(playerID);
    }

    /**
     * returns true if the player is the owner for the given gameId.
     * @return boolean of a player is owner of a game
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final boolean amIOwner() throws RemoteException {
        if (player.getGameInfo(this.gameID).getGameOwner().
                equals(player.getPlayerID())) {
            return true;
        }
        return false;
    }

    /**
     * leave game lobby.
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final void leaveGameLobby() throws RemoteException {
        this.gameID = 999;
        activeRobotIDs.clear();
        gameLobbyChatList.clear();
        try {
            player.leaveGameLobby();
        } catch (IllegalStateException exp) {
        }
    }

    /**
     * returns true if the given player is ready for init.
     * @param playerID needs the playerID as param
     * @return player.getPlayerInfo(playerID).getReadyState() boolean if
     * a player set ready
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final boolean getPlayerStatus(final String playerID)
            throws RemoteException {

        return player.getPlayerInfo(playerID).getReadyState();
    }

    /**
     * Returns a list of all available maps.
     * @return player.getMapInfos mapList
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final List getAvailableMaps() throws RemoteException {
        return player.getMapInfos();
    }

    /**
     * returns polling state.
     * @return isPooling boolean if is pooling or not
     */
    private boolean isPolling() {
        return isPolling;
    }

    public final void setGameResultList(GameOverCmd goc) {
        List<GameResultInfo> gi = goc.getPlayerList();
        this.gameResultList.clear();
        for (GameResultInfo gri : gi) {
            gameResultList.add("" + gri.getPlayerId() +
                    " | Coins: +" + gri.getExperience() +
                    " | Rank: " + gri.getResult());
        }
    }

    /**
     * returns the gameResults for a game
     * @return List of Players and points
     */
    public final List<String> getGameResults() {
        return this.gameResultList;
    }

    /**
     * Returns a list of a highscore. Needs as params the number of entries on
     * one page and the page number.
     * @param pageNumber the number of the selecte page
     * @return getStatPage Returns a list with highscores
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final List getHighScore(final int pageNumber)
            throws RemoteException {

        List<PlayerInfo> getPages = player.getRankingPage(namePerPage,
                pageNumber);
        List returnScores = new ArrayList();
        for (Iterator<PlayerInfo> it = getPages.iterator(); it.hasNext();) {
            PlayerInfo playerInfo = it.next();
            List tmp = new ArrayList();
            tmp.add(playerInfo.getRanking());
            tmp.add(playerInfo.getPlayerID());
            tmp.add(playerInfo.getWonMatches());
            tmp.add(playerInfo.getDrawMatches());
            tmp.add(playerInfo.getLostMatches());
            tmp.add(playerInfo.getExperience());
            tmp.add(playerInfo.getOnlineTime());
            tmp.add(playerInfo.getPlayingTime());
            tmp.add(playerInfo.getLevel());
            returnScores.add(tmp);
        }
        return returnScores;
    }

    /**
     * returns the maximum coin amount for a given level
     * @param maxPlayerLevel the maximum Level for the players in a game
     * @return the maximum coin amount for a given level
     * @throws java.rmi.RemoteException throws RemoteException
     */
    public final String getMaxCoinAmountByLevel(final Integer maxPlayerLevel)
            throws RemoteException {
        return "" + player.getCoins(maxPlayerLevel + 1);
    }
}
